﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using GraphEntities;
using info.lundin.math;
using NumericalFunctions;

namespace NumericalData.Interpolation.Data
{
    public class PointsGrid : IEnumerable
    {

        #region Fields
        protected Points pointsGrid;
        #endregion

        #region Constructors

        public PointsGrid()
        {
            pointsGrid = null;
        }

        public PointsGrid(double[][] values)
        {
            pointsGrid = new Points(values);
        }

        public PointsGrid(Points pts)
        {
            pointsGrid = pts;
        }
        #endregion

        #region Properties
        public Points Values
        {
            get
            {
                return pointsGrid;
            }
        }
        #endregion

        #region Operators
        public Point Item(int index)
        {
            return pointsGrid[index];
        }
        #endregion


        #region Interfaces
        public IEnumerator GetEnumerator()
        {
            return pointsGrid.GetEnumerator();
        }
        #endregion

    }

    public class EquidistantPointsGrid : PointsGrid
    {
        #region Fields
        private double? _step;
        #endregion

        #region Constructors

        public EquidistantPointsGrid()
        {
            _step = null;
        }


        public EquidistantPointsGrid(double[][] values)
            : base(values)
        {
            var pts = new Points(values);
            if (!IsEquidistant(pts))
            {
                base.pointsGrid = null;
                _step = null;
            }

            _step = GetStep(pts);

        }

        public EquidistantPointsGrid(Points pts)
            : base(pts)
        {
            if (!IsEquidistant(pts))
            {
                base.pointsGrid = null;
                _step = null;
            }

            _step = GetStep(pts);

        }



        #endregion

        #region Properties
        public double? Step
        {
            get
            {
                if (_step.HasValue)
                    return _step.Value;
                else
                    return null;
            }
        }

        public int Count
        {
            get
            {
                if (pointsGrid != null)
                    return pointsGrid.Count;
                return 0;
            }
        }

        #endregion

        #region Methods
        /// <summary>
        /// Get Point Grid From Function by evaluating
        /// </summary>
        /// <param name="expression">expression</param>
        /// <param name="variableName">name of variable</param>
        /// <param name="startX">start point</param>
        /// <param name="stopX">stop point</param>
        /// <param name="pointsNum">number of points</param>
        /// <returns>Grid of values</returns>
        public static EquidistantPointsGrid ExecuteFromFunction(string expression, string variableName, double startX, double stopX, int pointsNum)
        {
            if(startX > stopX)
                throw new Exception("Start coordinate shouldn't be greater than stop");
            double intervalLength = stopX - startX;
            double accuracy = intervalLength / (pointsNum - 1);
            double[] xPoints = FunctionValues.SplitIntervalWithPresicion(startX, stopX, accuracy);
            var points = CalculateValues(expression, variableName, xPoints);
            return new EquidistantPointsGrid(points);
        }

        public static EquidistantPointsGrid ExecuteFromFile(string path)
        {
            using (StreamReader reader = new StreamReader(path))
            {
                var points = new Points();
                var separators = new[] { ' ', '\t', ';' };
                var buf = "";
                while (!reader.EndOfStream)
                {
                    try
                    {
                        buf = reader.ReadLine();
                        var stringValues = buf.Split(separators);
                        var x = double.Parse(stringValues[0]);
                        var y = double.Parse(stringValues[1]);
                        points.Add(new Point(x, y));
                    }catch(Exception exc)
                    {
                        throw new Exception("File has wrong format");
                    }
                }
                return new EquidistantPointsGrid(points);
            }
        }

        private static Points CalculateValues(string expression, string variableName, double[] xPoints)
        {
            var parser = new ExpressionParser();
            var yPoints = new List<double>(xPoints.Length);
            var yValue = new Hashtable();

            foreach (double x in xPoints)
            {
                yValue.Add(variableName, x.ToString());
                var y = parser.Parse(expression, yValue);
                if(Double.IsNaN(y))
                    throw new Exception(String.Format("Function can't be calculated in {0}",x));
                yPoints.Add(y);
                yValue.Remove(variableName);
            }


            var points = new Points();
            for (int i = 0; i < xPoints.Length; ++i)
            {
                points.Add(new Point(xPoints[i], yPoints[i]));
            }

            return points;
        }

        private bool IsEquidistant(Points values)
        {
            if (values.Count <= 2)
                return true;
            double firstStep = GetStep(values);
            for (int i = 1; i < values.Count; ++i)
            {
                double step = values[i].X - values[i - 1].X;
                if (step - firstStep > 0.0000001)
                    return false;
            }
            return true;
        }

        private double GetStep(Points values)
        {
            return Math.Abs(values[1].X - values[0].X);
        }
        #endregion

    }
}
